package models

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"go-admin-official/guard"
	"go-admin-official/modules/config"
	"go-admin-official/modules/database"
	"golang.org/x/text/language"
	"html/template"
	"net/url"
	"strings"
	"time"
)

type PluginModel struct {
	Base

	Id                  int64         `json:"id"`
	Publisher           int64         `json:"publisher"`
	Author              string        `json:"author"`
	Title               string        `json:"title"`
	GoodNum             int64         `json:"good_num"`
	CommentNum          int64         `json:"comment_num"`
	Banners             []string      `json:"banners"`
	CategoryId          int64         `json:"category_id"`
	Name                string        `json:"name"`
	Uuid                string        `json:"uuid"`
	IdentifyCode        string        `json:"identify_code"`
	DownloadTimes       int64         `json:"download_times"`
	Description         string        `json:"description"`
	Version             string        `json:"version"`
	VersionHistory      []string      `json:"version_history"`
	Questions           []string      `json:"questions"`
	Features            string        `json:"features"`
	AuditState          int64         `json:"audit_state"`
	Price               []string      `json:"price"`
	GoodUUIDs           []string      `json:"good_uuids"`
	Readme              template.HTML `json:"readme"`
	AliasGoAdminVersion string        `json:"alias_goadmin_version"`

	Order int64 `json:"order"`

	CreateDate       string `json:"create_date"`
	UpdateDate       string `json:"update_date"`
	Website          string `json:"website"`
	Cover            string `json:"cover"`
	MiniCover        string `json:"mini_cover"`
	ModulePath       string `json:"module_path"`
	DownloadUrl      string `json:"download_url"`
	ExtraDownloadUrl string `json:"extra_download_url"`
	Translation      string `json:"translation"`

	Downloaded bool `json:"downloaded"`

	HasBought bool `json:"-"`

	CreatedAt   string `json:"created_at"`
	CreatedDate string `json:"-"`
	UpdatedAt   string `json:"updated_at"`
}

func Plugin() PluginModel {
	return PluginModel{Base: Base{Table: "plugins"}}
}

func (u PluginModel) WithTx(tx *database.SqlTx) PluginModel {
	u.Tx = tx
	return u
}

func (u PluginModel) WithID(id int64) PluginModel {
	u.Id = id
	return u
}

func (u PluginModel) Find(id interface{}) PluginModel {
	item, _ := database.Table(u.Table).Find(id)
	return u.MapToModel(item)
}

func (u PluginModel) GetDownloadPath() string {
	uu, _ := url.Parse(u.DownloadUrl)
	return uu.Path
}

func (u PluginModel) GetExtraDownloadPath() string {
	uu, _ := url.Parse(u.ExtraDownloadUrl)
	return uu.Path
}

func (u PluginModel) WithDownloadURL(uu string) PluginModel {
	u.DownloadUrl = uu
	return u
}

func (u PluginModel) AddBuyNum() error {
	_, err := database.SetTx(u.Tx).Table(u.Table).
		Where("id", "=", u.Id).
		UpdateRaw("buy_num = buy_num + 1").
		Exec()
	return err
}

func (u PluginModel) GetDownloadURL() string {
	return u.DownloadUrl + "?_upt=" + getUpToken(u.GetDownloadPath())
}

func getUpToken(path string) string {
	etime := fmt.Sprintf("%v", time.Now().Add(time.Second * 300).Unix())
	h := md5.New()
	h.Write([]byte(config.Get().UpyunSecurityToken + "&" + etime + "&" + path))
	md5str := hex.EncodeToString(h.Sum(nil))
	start := (len(md5str) - 8) / 2
	return md5str[start:start+8] + etime
}

func (u PluginModel) GetExtraDownloadURL() string {
	if u.ExtraDownloadUrl == "" {
		return ""
	}
	return u.ExtraDownloadUrl + "?_upt=" + getUpToken(u.GetExtraDownloadPath())
}

func (u PluginModel) FindByUUID(uuid string, param ...interface{}) PluginModel {
	item, _ := database.Table(u.Table).
		Where("uuid", "=", uuid).
		First()

	lang := ""

	if len(param) > 0 {
		lang = param[0].(string)
	}

	userID := int64(0)

	if len(param) > 1 {
		userID = param[1].(int64)
	}

	if translation, ok := item["translation"].(string); ok && lang != "" {
		var trans Translation
		if err := json.Unmarshal([]byte(translation), &trans); err == nil {
			item["title"] = trans.Get(lang, item["title"].(string))
			item["description"] = trans.Get(lang, item["description"].(string))
			item["author"] = trans.Get(lang, item["author"].(string))
		}
	}

	if userID != int64(0) {
		myBuyIDs := UserBuyPlugins().MyBuyPluginIDs(userID)

		hasBought := false
		for _, id := range myBuyIDs {
			if id == item["id"].(int64) {
				hasBought = true
				break
			}
		}
		item["has_bought"] = hasBought
	}

	return u.MapToModel(item)
}

type FilterType uint8

const (
	DefaultFilter = iota
	FreeFilter
	OfficialFilter
	CategoryFilter
)

var pluginListFields = []string{
	"id",
	"title",
	"publisher",
	"author",
	"good_num",
	"comment_num",
	"banners",
	"category_id",
	"name",
	"uuid",
	"identify_code",
	"download_times",
	"description",
	"version",
	"version_history",
	"questions",
	"features",
	"audit_state",
	"mini_cover",
	"price",
	"order",
	"create_date",
	"update_date",
	"website",
	"cover",
	"module_path",
	"download_url as url",
	"extra_download_url",
	"translation",
}

type Translation map[string]map[string]string

var LangMap = map[string]string{
	language.English.String():            "en",
	language.Chinese.String():            "cn",
	language.Japanese.String():           "jp",
	language.TraditionalChinese.String(): "tc",

	"en": "en",
	"cn": "cn",
	"jp": "jp",
	"tc": "tc",
}

func (trans Translation) Get(lang, word string) string {
	if l, ok := trans[LangMap[lang]]; ok {
		if w, ok := l[strings.ToLower(word)]; ok {
			return w
		} else {
			return word
		}
	} else {
		return word
	}
}

func (u PluginModel) List(param guard.PluginsParam, userID int64, api bool) ([]map[string]interface{}, int, bool) {

	var all []map[string]interface{}

	switch FilterType(param.Filter) {
	case DefaultFilter:
		all, _ = database.Table(u.Table).
			Select(pluginListFields...).
			Where("audit_state", "=", 1).
			OrderBy(param.Order, "desc").
			All()
	case FreeFilter:
		all, _ = database.Table(u.Table).
			Select(pluginListFields...).
			Where("audit_state", "=", 1).
			Where("price", "=", "").
			OrderBy(param.Order, "desc").
			All()
	case OfficialFilter:
		all, _ = database.Table(u.Table).
			Select(pluginListFields...).
			Where("audit_state", "=", 1).
			Where("publisher", "=", 1).
			OrderBy(param.Order, "desc").
			All()
	case CategoryFilter:
		all, _ = database.Table(u.Table).
			Select(pluginListFields...).
			Where("audit_state", "=", 1).
			Where("category_id", "=", param.CategoryID).
			OrderBy(param.Order, "desc").
			All()
	default:
		all, _ = database.Table(u.Table).
			Select(pluginListFields...).
			Where("audit_state", "=", 1).
			OrderBy(param.Order, "desc").
			All()
	}

	count := len(all)

	if param.Page*param.PageSize > count {
		all = all[(param.Page-1)*param.PageSize:]
	} else {
		all = all[(param.Page-1)*param.PageSize : param.Page*param.PageSize]
	}

	myBuyIDs := UserBuyPlugins().MyBuyPluginIDs(userID)

	for index, item := range all {

		if translation, ok := item["translation"].(string); ok {
			var trans Translation
			if err := json.Unmarshal([]byte(translation), &trans); err == nil {
				all[index]["title"] = trans.Get(param.Lang, item["title"].(string))
				all[index]["description"] = trans.Get(param.Lang, item["description"].(string))
				all[index]["author"] = trans.Get(param.Lang, item["author"].(string))
			}
		}

		delete(all[index], "translation")
		if api {
			// TODO: 解决时区问题
			all[index]["create_date"] = item["create_date"].(string) + "T00:00:00+08:00"
			all[index]["update_date"] = item["update_date"].(string) + "T00:00:00+08:00"
		}

		price, _ := all[index]["price"].(string)
		delete(all[index], "price")
		pp := make([]string, 0)
		if price != "" {
			pp = strings.Split(price, ",")
		}
		all[index]["price"] = pp

		// 查看是否购买
		hasBought := false
		for _, id := range myBuyIDs {
			if id == all[index]["id"].(int64) {
				hasBought = true
				break
			}
		}
		all[index]["has_bought"] = hasBought
		delete(all[index], "id")
	}

	return all, count, param.Page*param.PageSize < count
}

func (u PluginModel) Translate(lang string) PluginModel {
	if u.Translation != "" {
		var trans Translation
		if err := json.Unmarshal([]byte(u.Translation), &trans); err == nil {
			u.Title = trans.Get(lang, u.Title)
			u.Description = trans.Get(lang, u.Description)
			u.Author = trans.Get(lang, u.Author)
		}
	}
	return u
}

func (u PluginModel) MapToModel(m map[string]interface{}) PluginModel {

	u.Id, _ = m["id"].(int64)
	u.Publisher, _ = m["publisher"].(int64)
	u.Author, _ = m["author"].(string)
	u.Title, _ = m["title"].(string)
	u.GoodNum, _ = m["good_num"].(int64)
	u.CommentNum, _ = m["comment_num"].(int64)
	banners, _ := m["banners"].(string)
	if banners != "" {
		u.Banners = strings.Split(banners, ",")
	}
	u.CategoryId, _ = m["category_id"].(int64)
	u.Name, _ = m["name"].(string)
	u.Uuid, _ = m["uuid"].(string)
	u.IdentifyCode, _ = m["identify_code"].(string)
	u.DownloadTimes, _ = m["download_times"].(int64)
	u.Description, _ = m["description"].(string)
	u.Version, _ = m["version"].(string)
	history, _ := m["version_history"].(string)
	if history != "" {
		u.VersionHistory = strings.Split(history, ",")
	}
	questions, _ := m["questions"].(string)
	if questions != "" {
		u.Questions = strings.Split(questions, ",")
	}
	u.Features, _ = m["features"].(string)
	u.AuditState, _ = m["audit_state"].(int64)
	price, _ := m["price"].(string)
	if price != "" {
		u.Price = strings.Split(price, ",")
	}
	uuids, _ := m["good_uuids"].(string)
	if uuids != "" {
		u.GoodUUIDs = strings.Split(uuids, ",")
	}
	u.CreatedAt, _ = m["created_at"].(string)
	u.CreatedDate = strings.Split(u.CreatedAt, " ")[0]
	u.UpdatedAt, _ = m["updated_at"].(string)
	readme, _ := m["readme"].(string)
	u.Readme = template.HTML(readme)

	u.AliasGoAdminVersion, _ = m["alias_goadmin_version"].(string)
	u.CreateDate, _ = m["create_date"].(string)
	u.UpdateDate, _ = m["update_date"].(string)
	u.Website, _ = m["website"].(string)
	u.Cover, _ = m["cover"].(string)
	u.MiniCover, _ = m["mini_cover"].(string)
	u.ModulePath, _ = m["module_path"].(string)
	u.DownloadUrl, _ = m["download_url"].(string)
	u.ExtraDownloadUrl, _ = m["extra_download_url"].(string)
	u.Translation, _ = m["translation"].(string)
	u.HasBought, _ = m["has_bought"].(bool)

	return u
}
